package cn.south.campus.business.service.impl;

import cn.south.campus.business.mapper.ExpressTakeMapper;
import cn.south.campus.business.model.bo.ExpressTakeType;
import cn.south.campus.business.model.bo.LoginContext;
import cn.south.campus.business.model.bo.LoginInfo;
import cn.south.campus.business.model.constants.CacheKey;
import cn.south.campus.business.model.entity.*;
import cn.south.campus.business.model.enums.*;
import cn.south.campus.business.model.req.ApplyTakeRecordReq;
import cn.south.campus.business.model.req.ExpressTakeReq;
import cn.south.campus.business.model.vo.*;
import cn.south.campus.business.service.*;
import cn.south.campus.common.cache.CacheService;
import cn.south.campus.common.exceptions.BusinessException;
import cn.south.campus.common.model.enums.CommonEnums;
import cn.south.campus.common.pay.PayFactory;
import cn.south.campus.common.pay.PayService;
import cn.south.campus.common.utils.AssertUtils;
import cn.south.campus.common.utils.SnowflakeIdWorker;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 快递代取 服务实现类
 * </p>
 *
 * @author chow
 * @since 2024-12-13
 */
@Service
@AllArgsConstructor
public class ExpressTakeServiceImpl extends ServiceImpl<ExpressTakeMapper, ExpressTake> implements ExpressTakeService {

    private final ExpressTakeMapper expressTakeMapper;

    private final CacheService cacheService;

    private final ExpressTakeDetailService expressTakeDetailService;

    private final ExpressApplyService expressApplyService;

    private final ExpressTakeOrderService expressTakeOrderService;

    private final UserService userService;

    private final StudentService studentService;

    private final TransactionTemplate transactionTemplate;

    private final WalletService walletService;

    private final WalletLogService walletLogService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String publish(ExpressTakeVo param) {
        LoginInfo context = LoginContext.getContext();
        int authStatus = studentService.getAuthStatus(context.getId());
        if (authStatus != StudentEnums.AuthStatusEnums.AUTH_SUCCESS.getCode()) {
            throw new BusinessException(CampusErrorEnums.STUDENT_AUTH_ERROR);
        }
        String config = cacheService.getCacheMapValue(CacheKey.CONFIG, "EXPRESS_TAKE_TYPE");
        String configValue = JSON.parseObject(config, Config.class).getConfigValue();
        List<ExpressTakeType> expressTakeTypes = JSON.parseArray(configValue, ExpressTakeType.class);
        Map<String, BigDecimal> typePriceMap = expressTakeTypes.stream().collect(Collectors.toMap(ExpressTakeType::getType, ExpressTakeType::getPrice));
        BigDecimal price = BigDecimal.ZERO;
        for (ExpressTakeDetailVo detail : param.getExpressTakeDetails()) {
            AssertUtils.isEquals(typePriceMap.containsKey(detail.getType()), true, CommonEnums.SYSTEM_ERROR);
            detail.setPrice(typePriceMap.get(detail.getType()));
            price = price.add(detail.getPrice());
        }
        ExpressTake expressTake = ExpressTakeVo.convert(param);
        expressTake.setUserId(context.getId());
        expressTake.setPrice(price);
        expressTake.setStatus(ExpressTakeEnums.TakeStatus.WAIT_TAKE.getCode());
        expressTake.setBizNo(String.valueOf(SnowflakeIdWorker.getId()));
        expressTake.setAmount(param.getExpressTakeDetails().size());
        expressTakeMapper.insert(expressTake);
        Long takeId = expressTake.getId();
        param.getExpressTakeDetails().forEach(detail -> detail.setExpressTakeId(takeId));
        expressTakeDetailService.batchInsertDetail(param.getExpressTakeDetails());
        return expressTakeOrderService.createOrder(expressTake);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void payOrder(String orderNo, String payMethod) {
        ExpressTakeOrder takeOrder = expressTakeOrderService.selectOrderNo(orderNo);
        AssertUtils.isEquals(takeOrder.getPayStatus(), OrderEnums.OrderPayStatus.UNPAID.getCode(), CommonEnums.SYSTEM_ERROR);
        if (!payMethod.equals("wallet")) {
            PayService payService = PayFactory.getPayService(payMethod);
            payService.pay(orderNo, takeOrder.getPrice());
            takeOrder.setPayStatus(OrderEnums.OrderPayStatus.PAID.getCode());
            takeOrder.setPayMethod(payMethod);
            takeOrder.setPayTime(LocalDateTime.now());
            takeOrder.setStatus(OrderEnums.OrderStatus.FINISHED.getCode());
            expressTakeOrderService.updateById(takeOrder);
            return;
        }
        ExpressTake expressTake = expressTakeMapper.selectById(takeOrder.getExpressTakeId());
        AssertUtils.isNotNull(expressTake, CommonEnums.SYSTEM_ERROR);
        //查询钱包
        boolean lock = cacheService.lock(CacheKey.WALLET + ":" + expressTake.getUserId(), 1000 * 2);
        if (!lock) {
            throw new BusinessException(CommonEnums.CONCURRENT_ERROR);
        }
        try {
            transactionTemplate.executeWithoutResult(transactionStatus -> {
                //买家扣款
                walletService.subBalanceAddFreeze(takeOrder.getPrice(), expressTake.getUserId());
                //记录流水
                walletLogService.insertLog(takeOrder.getPrice(), expressTake.getUserId(), orderNo, WalletEnums.WalletTypeEnums.PAY, WalletEnums.WalletLogStatusEnums.PAYED);
                takeOrder.setPayStatus(OrderEnums.OrderPayStatus.PAID.getCode());
                takeOrder.setPayMethod(payMethod);
                takeOrder.setPayTime(LocalDateTime.now());
                takeOrder.setStatus(OrderEnums.OrderStatus.FINISHED.getCode());
                expressTakeOrderService.updateById(takeOrder);
            });
        } finally {
            cacheService.unLock(CacheKey.WALLET + ":" + expressTake.getUserId());
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        LoginInfo context = LoginContext.getContext();
        ExpressTake expressTake = expressTakeMapper.selectById(id);
        AssertUtils.isNotNull(expressTake, CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(context.getId(), expressTake.getUserId(), CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(expressTake.getStatus(), ExpressTakeEnums.TakeStatus.TAKE_CANCEL.getCode(), CommonEnums.SYSTEM_ERROR);
        expressTakeMapper.deleteById(id);
        expressTakeDetailService.delByTakeId(id);
        expressTakeOrderService.delByTakeId(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(Long id) {
        LoginInfo context = LoginContext.getContext();
        ExpressTake expressTake = expressTakeMapper.selectById(id);
        AssertUtils.isNotNull(expressTake, CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(context.getId(), expressTake.getUserId(), CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(expressTake.getStatus(), ExpressTakeEnums.TakeStatus.WAIT_TAKE.getCode(), CampusErrorEnums.ORDER_STATUS_ERROR);
        expressTake.setStatus(ExpressTakeEnums.TakeStatus.TAKE_CANCEL.getCode());
        //查询钱包
        boolean lock = cacheService.lock(CacheKey.WALLET + ":" + expressTake.getUserId(), 1000 * 2);
        if (!lock) {
            throw new BusinessException(CommonEnums.CONCURRENT_ERROR);
        }
        try {
            transactionTemplate.executeWithoutResult(transactionStatus -> {
                //退款
                walletService.subFreezeAddBalance(expressTake.getPrice(), expressTake.getUserId());
                String orderNo = expressTakeOrderService.delByTakeId(id);
                expressTakeMapper.updateById(expressTake);
                walletLogService.insertLog(expressTake.getPrice(), expressTake.getUserId(), orderNo, WalletEnums.WalletTypeEnums.REFUND, WalletEnums.WalletLogStatusEnums.RECEIVED);
            });
        } finally {
            cacheService.unLock(CacheKey.WALLET + ":" + expressTake.getUserId());
        }
    }

    @Override
    public ExpressTakeVo getDetail(Long id) {
        ExpressTake expressTake = expressTakeMapper.queryDetail(id);
        AssertUtils.isNotNull(expressTake, CommonEnums.DATA_HAS_NOT_ERROR);
        ExpressTakeVo expressTakeVo = ExpressTakeVo.convert(expressTake);
        expressTakeVo.getExpressTakeDetails().forEach(detail -> {
            detail.setTakeCode(null);
            detail.setPicture(null);
        });
        expressTakeVo.setApplyStatus(applyStatus(id));
        return expressTakeVo;
    }

    @Override
    public ExpressTakeVo getDetailMe(Long id) {
        LoginInfo context = LoginContext.getContext();
        ExpressTake expressTake = expressTakeMapper.queryDetail(id);
        AssertUtils.isNotNull(expressTake, CommonEnums.DATA_HAS_NOT_ERROR);
        AssertUtils.isEquals(context.getId(), expressTake.getUserId(), CommonEnums.SYSTEM_ERROR);
        ExpressTakeVo expressTakeVo = ExpressTakeVo.convert(expressTake);
        expressTakeVo.setApplyStatus(applyStatus(id));
        return expressTakeVo;
    }

    @Override
    public IPage<ExpressTakeVo> pageQuery(ExpressTakeReq req) {
        LoginInfo context = LoginContext.getContext();
        req.setSexLimit(Arrays.asList(context.getGender(), Gender.UNKNOWN.getCode()));
        req.setUserId(context.getId());
        if (req.getDeliveryTime() != null && req.getDeliveryTime() == 0) {
            req.setDeliveryTime(null);
        }
        PageDTO<ExpressTake> pageDTO = expressTakeMapper.pageQuery(req.getPageDto(), req);
        return pageDTO.convert(ExpressTakeVo::convert);
    }

    @Override
    public IPage<ExpressTakeVo> pageQueryMe(ExpressTakeReq req) {
        LoginInfo context = LoginContext.getContext();
        req.setMe(true);
        req.setUserId(context.getId());
        return pageQuery(req);
    }

    @Override
    public void apply(Long id) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        UserVo userVo = userService.detail(userId);
        int authStatus = studentService.getAuthStatus(userId);
        if (authStatus != StudentEnums.AuthStatusEnums.AUTH_SUCCESS.getCode()) {
            throw new BusinessException(CampusErrorEnums.STUDENT_AUTH_ERROR);
        }
        ExpressTakeVo detail = getDetail(id);
        Integer status = detail.getStatus();
        AssertUtils.isEquals(status, ExpressTakeEnums.TakeStatus.WAIT_TAKE.getCode(), CampusErrorEnums.EXPRESS_TAKE_HAVE_APPLY_ERROR);
        Integer sexLimit = detail.getSexLimit();
        if (sexLimit != null && sexLimit.equals(userVo.getGender())) {
            throw new BusinessException(CampusErrorEnums.EXPRESS_TAKE_SEX_LIMIT);
        }
        ExpressApply apply = new ExpressApply();
        apply.setUserId(userId);
        apply.setExpressTakeId(id);
        apply.setPublisher(detail.getUserId());
        apply.setStatus(ExpressTakeEnums.TakeApplyStatus.APPLYING.getCode());
        apply.setApplyTime(LocalDateTime.now());
        expressApplyService.save(apply);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void accept(Long applyId) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        ExpressApply expressApply = expressApplyService.getById(applyId);
        AssertUtils.isNotNull(expressApply, CampusErrorEnums.EXPRESS_APPLY_NOT_EXIST);
        Long expressTakeId = expressApply.getExpressTakeId();
        ExpressTake expressTake = getById(expressTakeId);
        AssertUtils.isEquals(expressTake.getUserId(), userId, CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(expressApply.getStatus(), ExpressTakeEnums.TakeApplyStatus.APPLYING.getCode(), CampusErrorEnums.EXPRESS_TAKE_HAVE_APPLY_ERROR);
        expressApply.setStatus(ExpressTakeEnums.TakeApplyStatus.PASS.getCode());
        expressApplyService.updateById(expressApply);
        expressApplyService.rejectTakeApply(expressTakeId, applyId);
        expressTake.setStatus(ExpressTakeEnums.TakeStatus.TAKE.getCode());
        updateById(expressTake);
    }

    @Override
    public List<ExpressApplyVo> applyRecord(Long takeId, String keyword) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        return expressApplyService.applyRecord(takeId, userId, keyword);
    }

    @Override
    public Integer applyStatus(Long takeId) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        return expressApplyService.applyStatus(takeId, userId);
    }

    @Override
    public ExpressTakeOrderVo takeOrder(String orderNo) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        ExpressTakeOrder takeOrder = expressTakeOrderService.selectOrderNo(orderNo);
        AssertUtils.isNotNull(takeOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        ExpressTake expressTake = expressTakeMapper.selectById(takeOrder.getExpressTakeId());
        AssertUtils.isNotNull(expressTake, CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(expressTake.getUserId(), userId, CommonEnums.SYSTEM_ERROR);
        return ExpressTakeOrderVo.convert(takeOrder);
    }

    @Override
    public ExpressTakeVo getTakeByOrderNo(String orderNo) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        ExpressTakeOrder takeOrder = expressTakeOrderService.selectOrderNo(orderNo);
        AssertUtils.isNotNull(takeOrder, CommonEnums.DATA_HAS_NOT_ERROR);
        ExpressTake expressTake = expressTakeMapper.queryDetail(takeOrder.getExpressTakeId());
        AssertUtils.isNotNull(expressTake, CommonEnums.SYSTEM_ERROR);
        AssertUtils.isEquals(expressTake.getUserId(), userId, CommonEnums.SYSTEM_ERROR);
        return ExpressTakeVo.convert(expressTake);
    }

    @Override
    public IPage<ExpressTakeVo> queryMyApplyExpress(ApplyTakeRecordReq req) {
        LoginInfo context = LoginContext.getContext();
        req.setUserId(context.getId());
        IPage<ExpressTake> page = expressTakeMapper.queryMyApplyExpress(req.getPageDto(), req);
        return page.convert(ExpressTakeVo::convert);
    }

    @Override
    public List<ExpressTakeDetailVo> showTakeInfo(Long takeId) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        Integer applyStatus = expressApplyService.applyStatus(takeId, userId);
        AssertUtils.isEquals(applyStatus, ExpressTakeEnums.TakeApplyStatus.PASS.getCode(), CommonEnums.SYSTEM_ERROR);
        return expressTakeDetailService.selectByTakeId(takeId);
    }

    @Override
    public void confirmSend(Long takeId) {
        LoginInfo context = LoginContext.getContext();
        Long userId = context.getId();
        Integer applyStatus = expressApplyService.applyStatus(takeId, userId);
        AssertUtils.isEquals(applyStatus, ExpressTakeEnums.TakeApplyStatus.PASS.getCode(), CommonEnums.SYSTEM_ERROR);
        ExpressTake expressTake = expressTakeMapper.selectById(takeId);
        AssertUtils.isNotNull(expressTake, CommonEnums.SYSTEM_ERROR);
        expressTake.setStatus(ExpressTakeEnums.TakeStatus.TAKE_SEND.getCode());
        updateById(expressTake);
    }

    @Override
    public void confirmReceive(Long takeId) {
        LoginInfo context = LoginContext.getContext();
        ExpressTake expressTake = expressTakeMapper.selectById(takeId);
        AssertUtils.isNotNull(expressTake, CommonEnums.DATA_HAS_NOT_ERROR);
        AssertUtils.isEquals(expressTake.getStatus(), ExpressTakeEnums.TakeStatus.TAKE_SEND.getCode(), CampusErrorEnums.EXPRESS_TAKE_HAVE_APPLY_ERROR);
        AssertUtils.isEquals(expressTake.getUserId(), context.getId(), CommonEnums.SYSTEM_ERROR);
        expressTake.setStatus(ExpressTakeEnums.TakeStatus.TAKE_FINISH.getCode());
        ExpressApply expressApply = expressApplyService.queryAcceptRecord(takeId);
        AssertUtils.isNotNull(expressApply, CommonEnums.SYSTEM_ERROR);
        //扣除冻结金额，接单者获得奖励
        transactionTemplate.executeWithoutResult(transactionStatus -> {
            updateById(expressTake);
            walletService.subFreezeBalance(expressTake.getPrice(), expressTake.getUserId());
            walletService.addBalance(expressTake.getPrice(), expressApply.getUserId());
            walletLogService.insertLog(expressTake.getPrice(), expressApply.getUserId(), expressTake.getBizNo(), WalletEnums.WalletTypeEnums.INCOME, WalletEnums.WalletLogStatusEnums.RECEIVED);
        });
    }
}
